package org.turbobytes.unikey.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.turbobytes.unikey.util.exception.ReflectionException;

import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.LinkedHashMap;

/**
 * TODO: Document me
 *
 * @author Ganeshji Marwaha
 * @since Aug 7, 2010
 */
public class ReflectionUtil {
  private static Log log = LogFactory.getLog(ReflectionUtil.class);

  public static LinkedHashMap<String, Method> getters = new LinkedHashMap<String, Method>() {
    private static final int MAX_ENTRIES = 1000;

    public boolean removeEldestEntry(Map.Entry<String, Method> eldest) {
      return size() > MAX_ENTRIES;
    }

  };

  /**
   * Invoke the get+fieldName method of object o
   *
   * @param o
   * @param fieldName
   * @return
   */
  public static Object invokeGetter(Object o, String fieldName) {
    Class c = o.getClass();
    String key = c.getSimpleName() + "#" + fieldName;
    try {
      Method getter = getters.get(key);
      if (getter == null) {
        getter = findGetter(c, fieldName, "get");
        if (getter == null) getter = findGetter(c, fieldName, "is");
        getters.put(key, getter);
      }
      return getter.invoke(o, new Object[0]);
    } catch (Exception e) {
      System.out.println("Could not find getter for " + fieldName + " " + e.getMessage());
    }
    return null;
  }

  private static Method findGetter(Class c, String fieldName, String prefix) {
    Method getter = null;
    try {
      getter = c.getMethod(prefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1), new Class[0]);
    } catch (Exception e) {
      System.out.println("Exception while finding getter" + e.getMessage());
    }
    return getter;
  }

  public static Field getField(Object obj, String fieldStr, boolean forceAccesible) {
    try {
      Field field = obj.getClass().getDeclaredField(fieldStr);
      if ((forceAccesible) && (!Modifier.isPublic(field.getModifiers()))) {
        field.setAccessible(true);
      }
      return field;
    } catch (Exception e) {
      throw new ReflectionException("ReflectionException", e);
    }
  }

  public static Object getFieldValue(Object obj, Field field) {
    try {
      return field.get(obj);
    } catch (Exception e) {
      throw new ReflectionException("ReflectionException", e);
    }
  }

  public static void setFieldValue(Object obj, Field field, Object value) {
    try {
      field.set(obj, value);
    } catch (Exception e) {
      throw new ReflectionException("ReflectionException", e);
    }
  }

  public static <D> D newInstance(Class<D> clazz) {
    try {
      return clazz.newInstance();
    } catch (Exception e) {
      throw new ReflectionException("ReflectionException", e);
    }
  }

  public static <D> D newInstance(Type type) {
    return newInstance((Class<D>) type);
  }
}
